﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Text;
using System.Threading;
using System.Net;
using System.Net.Sockets;

namespace Org.Alexander.Edas
{
    public class Connector
    {
        private bool isClose;
        private Socket worker;
        private SocketAsyncEventArgs connectArg;
        private SocketAsyncEventArgs readArg;
        private SocketAsyncEventArgs writeArg;
        private byte[] ReadBuffer;
        private AutoResetEvent ConnectLock;
        private AutoResetEvent ReadLock;
        private AutoResetEvent WriteLock;
        private AutoResetEvent ErrorLock;
        private int bufferSize;

        public int ID
        {
            get;
            set;
        }

        public byte[] Buffer
        {
            get 
            {
                return ReadBuffer;
            }
        }
        public Object StateObject
        {
            get;
            set;
        }

        public event ConnectorDataHandler OnRead;
        public event ConnectorOperateHanlder OnConnected;
        public event ConnectorOperateHanlder OnSend;
        public event ConnectorOperateHanlder OnInited;
        public event ConnectorMassageHandler OnClose;
        public event SocketErrorHandler OnError;



        public Connector(string IP,int Port,int BufferSize)
        {
            bufferSize = BufferSize;
            worker = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.IP);
            worker.Bind(
                new IPEndPoint(
                    IPAddress.Parse(IP), Port
                    )
                    );
            connectArg = new SocketAsyncEventArgs();
            connectArg.Completed += new EventHandler<SocketAsyncEventArgs>(connectArg_Completed);
            InitResource(BufferSize);
        }

        public Connector(int BufferSize,int ConnectID)
        {
            bufferSize = BufferSize;
            ID = ConnectID;
            InitResource(BufferSize);
        }

        public void Init(Socket WorkingSock)
        {
            isClose = false;
            worker = WorkingSock;
            worker.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendTimeout, 1000);
            ReadBuffer.Initialize();

        }
        public void Retrieve()
        {
            //InitResource(bufferSize);
        }

        private void InitResource(int BufferSize)
        {
            ConnectLock = new AutoResetEvent(true);
            WriteLock = new AutoResetEvent(true);
            ReadLock = new AutoResetEvent(true);
            ErrorLock = new AutoResetEvent(true);
            readArg = new SocketAsyncEventArgs();
            writeArg = new SocketAsyncEventArgs();
            ReadBuffer = new byte[BufferSize];
            readArg.SetBuffer(ReadBuffer, 0, BufferSize);
            readArg.Completed += new EventHandler<SocketAsyncEventArgs>(readArg_Completed);
            writeArg.Completed += new EventHandler<SocketAsyncEventArgs>(writeArg_Completed);
            if (OnInited != null)
            {
                OnInited(this);
            }
        }

        public void Close()
        {
            if (OnClose!= null)
            {
                OnClose("Session closed by manual");
            }
            Release();
        }

        private void Release()
        {
            isClose = true;
            ConnectorPool.GetIns().ReleaseConnector(ID);
            StateObject = null;
            try
            {
                if (worker.Connected)
                {
                    worker.Shutdown(SocketShutdown.Both);
                    worker.Close();
                }
            }
            catch (Exception ex)
            {
            }
        }

        private void Close(Exception ex)
        {
            if (OnError != null)
            {
                OnError(this, ex);
            }
            if (OnClose != null)
            {
                OnClose("Session closed by Exception" + ex.Message);
            }
            Release();
        }

        public void Send(byte[] data)
        {
            if (!isClose)
            {
                try
                {
                    lock (worker)
                    {
                        writeArg.SetBuffer(data, 0, data.Length);
                        if (!worker.SendAsync(writeArg))
                        {
                            ProcessWrite(writeArg);
                        }
                    }
                }
                catch (Exception ex)
                {
                    Close(ex);
                }
            }
        }

        public void WaitingRead()
        {
            if (!isClose)
            {
                lock (worker)
                {
                    try
                    {

                        if (!worker.ReceiveAsync(readArg))
                        {
                            ProccessRead(readArg);
                        }
                    }
                    catch (Exception ex)
                    {
                        Close(ex);
                    }
                }
            }
        }
        public void Connect(string IP, int Port)
        {
            connectArg.RemoteEndPoint = new IPEndPoint(
                IPAddress.Parse(IP), Port
                );
            lock (worker)
            {
                if (!worker.ConnectAsync(connectArg))
                {
                    ProccessConnect(connectArg);
                }
            }
        }

        void writeArg_Completed(object sender, SocketAsyncEventArgs e)
        {
            if (!isClose)
            {
                WriteLock.WaitOne();
                //throw new NotImplementedException();
                ProcessWrite(e);
                WriteLock.Set();
            }
        }

        private void ProcessWrite(SocketAsyncEventArgs e)
        {
            try
            {
                if (e.SocketError == SocketError.Success)
                {
                    if (OnSend != null)
                    {
                        OnSend(this);
                    }
                }
                else
                {
                    WhenError(e);
                }
            }
            catch (Exception ex)
            {
                Close(ex);
            }
        }

        private void WhenError(SocketAsyncEventArgs e)
        {
            string dis = e.SocketError.ToString();
            Exception ex = new Exception(dis);
            Close(ex);
        }


        void readArg_Completed(object sender, SocketAsyncEventArgs e)
        {
            if (!isClose)
            {
                ReadLock.WaitOne();
                //throw new NotImplementedException();
                ProccessRead(e);
                ReadLock.Set();
            }
        }

        private void ProccessRead(SocketAsyncEventArgs e)
        {
            try
            {
                if (e.SocketError == SocketError.Success)
                {
                    if (OnRead != null)
                    {
                        OnRead(this, e.BytesTransferred);
                    }
                }
                else
                {
                    WhenError(e);
                }
            }
            catch (Exception ex)
            {
                Close(ex);
            }
        }

        void connectArg_Completed(object sender, SocketAsyncEventArgs e)
        {
            if (!isClose)
            {
                ConnectLock.WaitOne();
                //throw new NotImplementedException();
                ProccessConnect(e);
                ConnectLock.Set();
            }
        }

        private void ProccessConnect(SocketAsyncEventArgs e)
        {
            try
            {
                if (e.SocketError == SocketError.Success)
                {
                    if (OnConnected != null)
                    {
                        OnConnected(this);
                    }
                }
                else
                {
                    WhenError(e);
                }
            }
            catch (Exception ex)
            {
                Close(ex);
            }
        }


    }
}
